﻿using PMS.Core.Caching;
using PMS.Data.DbUtils;
using PMS.Data.Entities.Messages;
using PMS.Data.Entities.Stores;
using PMS.Services.Caching;
using PMS.Services.Caching.Extensions;
using PMS.Services.EntityServices.Stores;
using PMS.Services.Utils;
using System;
using System.Collections.Generic;
using System.Linq;

namespace PMS.Services.EntityServices.Messages
{
    /// <summary>
    /// Message template service
    /// </summary>
    public partial class EmailMessageTemplateService : IEmailMessageTemplateService
    {
        #region Fields

        private readonly ICacheKeyService _cacheKeyService;
        private readonly IEventDispatcher _eventDispatcher;
        private readonly IStaticCacheManager _staticCacheManager;
        private readonly IStoreMappingService _storeMappingService;
        private readonly IRepository<EmailMessageTemplate> _messageTemplateRepository;
        private readonly IRepository<StoreMapping> _storeMappingRepository;

        #endregion

        #region Ctor

        public EmailMessageTemplateService(ICacheKeyService cacheKeyService,
            IEventDispatcher eventDispatcher,
            IStaticCacheManager staticCacheManager,
            IStoreMappingService storeMappingService,
            IRepository<StoreMapping> storeMappingRepository,
            IRepository<EmailMessageTemplate> messageTemplateRepository)
        {
            _cacheKeyService = cacheKeyService;
            _eventDispatcher = eventDispatcher;
            _staticCacheManager = staticCacheManager;
            _storeMappingService = storeMappingService;
            _storeMappingRepository = storeMappingRepository;
            _messageTemplateRepository = messageTemplateRepository;
        }

        #endregion

        #region Methods

        /// <summary>
        /// Delete a message template
        /// </summary>
        /// <param name="messageTemplate">Message template</param>
        public virtual void DeleteMessageTemplate(EmailMessageTemplate messageTemplate)
        {
            if (messageTemplate == null)
                throw new ArgumentNullException(nameof(messageTemplate));

            _messageTemplateRepository.Delete(messageTemplate);

            //event notification
            _eventDispatcher.EntityDeleted(messageTemplate);
        }

        /// <summary>
        /// Inserts a message template
        /// </summary>
        /// <param name="messageTemplate">Message template</param>
        public virtual void InsertMessageTemplate(EmailMessageTemplate messageTemplate)
        {
            if (messageTemplate == null)
                throw new ArgumentNullException(nameof(messageTemplate));

            messageTemplate.IsActive = true;
            messageTemplate.CreatedOnTimeUtc = messageTemplate.UpdatedOnTimeUtc = DateTime.UtcNow;
            _messageTemplateRepository.Insert(messageTemplate);

            //event notification
            _eventDispatcher.EntityInserted(messageTemplate);
        }

        /// <summary>
        /// Updates a message template
        /// </summary>
        /// <param name="messageTemplate">Message template</param>
        public virtual void UpdateMessageTemplate(EmailMessageTemplate messageTemplate)
        {
            if (messageTemplate == null)
                throw new ArgumentNullException(nameof(messageTemplate));

            messageTemplate.UpdatedOnTimeUtc = DateTime.UtcNow;
            _messageTemplateRepository.Update(messageTemplate);

            //event notification
            _eventDispatcher.EntityUpdated(messageTemplate);
        }

        /// <summary>
        /// Gets a message template
        /// </summary>
        /// <param name="messageTemplateId">Message template identifier</param>
        /// <returns>Message template</returns>
        public virtual EmailMessageTemplate GetMessageTemplateById(int messageTemplateId)
        {
            if (messageTemplateId == 0)
                return null;

            return _messageTemplateRepository.ToCachedGetById(messageTemplateId);
        }

        /// <summary>
        /// Gets message templates by the name
        /// </summary>
        /// <param name="messageTemplateName">Message template name</param>
        /// <param name="storeId">Store identifier; pass null to load all records</param>
        /// <returns>List of message templates</returns>
        public virtual IList<EmailMessageTemplate> GetMessageTemplatesByName(string messageTemplateName, int? storeId = null)
        {
            if (string.IsNullOrWhiteSpace(messageTemplateName))
                throw new ArgumentException(nameof(messageTemplateName));

            var key = _cacheKeyService.PrepareKeyForDefaultCache(CacheKeyConfigs.EmailMessageTemplatesByNameCacheKey, messageTemplateName, storeId);

            return _staticCacheManager.Get(key, () =>
            {
                //get message templates with the passed name
                var templates = _messageTemplateRepository.Table
                    .Where(messageTemplate => messageTemplate.IsActive && messageTemplate.TemplateName.Equals(messageTemplateName))
                    .OrderBy(messageTemplate => messageTemplate.Id).ToList();

                //filter by the store
                if (storeId.HasValue && storeId.Value > 0)
                    templates = templates.Where(messageTemplate => _storeMappingService.Authorize(messageTemplate, storeId.Value)).ToList();

                return templates;
            });
        }

        /// <summary>
        /// Gets all message templates
        /// </summary>
        /// <param name="storeId">Store identifier; pass 0 to load all records</param>
        /// <returns>Message template list</returns>
        public virtual IList<EmailMessageTemplate> GetAllMessageTemplates(int storeId)
        {
            var key = _cacheKeyService.PrepareKeyForDefaultCache(CacheKeyConfigs.EmailMessageTemplatesAllCacheKey, storeId);

            var query = _messageTemplateRepository.Table.Where(t => t.IsActive);
            query = query.OrderBy(t => t.TemplateName);

            if (storeId <= 0)
                return query.ToCachedList(key);

            //store mapping
            query = from t in query
                    join sm in _storeMappingRepository.Table
                        on new
                        {
                            c1 = t.Id,
                            c2 = nameof(EmailMessageTemplate)
                        }
                        equals new
                        {
                            c1 = sm.EntityId,
                            c2 = sm.EntityName
                        }
                        into tSm
                    from sm in tSm.DefaultIfEmpty()
                    where !t.LimitedToStores || storeId == sm.StoreId
                    select t;

            query = query.Distinct().OrderBy(t => t.TemplateName);

            return query.ToCachedList(key);
        }

        /// <summary>
        /// Create a copy of message template with all depended data
        /// </summary>
        /// <param name="messageTemplate">Message template</param>
        /// <returns>Message template copy</returns>
        public virtual EmailMessageTemplate CopyMessageTemplate(EmailMessageTemplate messageTemplate)
        {
            if (messageTemplate == null)
                throw new ArgumentNullException(nameof(messageTemplate));

            var mtCopy = new EmailMessageTemplate
            {
                TemplateName = messageTemplate.TemplateName,
                BccEmailAddresses = messageTemplate.BccEmailAddresses,
                Subject = messageTemplate.Subject,
                Body = messageTemplate.Body,
                IsActive = messageTemplate.IsActive,
                EmailAccountId = messageTemplate.EmailAccountId,
                LimitedToStores = messageTemplate.LimitedToStores,
                DelayPeriodMinutes = messageTemplate.DelayPeriodMinutes,
            };

            InsertMessageTemplate(mtCopy);

            //store mapping
            var selectedStoreIds = _storeMappingService.GetStoresIdsWithAccess(messageTemplate);
            foreach (var id in selectedStoreIds)
            {
                _storeMappingService.InsertStoreMapping(mtCopy, id);
            }

            return mtCopy;
        }

        #endregion
    }
}
